<?php namespace nimo\common\design;

use Closure;

class PComposite {

	private $fields = null;
	private $parent = null;
	private $children = array();

    public function __construct($parent, $fields) {
    	$this->parent = $parent;
    	$this->fields = $fields;
    }

    public function fields(){
        return $this->fields;
    }

    public function set_field($key, $value){
        $this->fields[$key] = $value;
    }

    public function is_root(){
        return ($this->parent == null);
    }

    public function parent(){
    	return $this->parent;
    }

    public function set_parent(PComposite $parent){
    	$this->parent = $parent;
    }

    public function add(PComposite $child) {
        $this->children[] = $child;
    }

    public function children(){
        return $this->children;
    }

    public function has_child(){
        return (count($this->children) > 0);
    }

    public function index_of_child($obj){
        foreach ($this->children as $key => $child) {
            if ($child == $obj) {
                return $key;
            }
        }
        return -1;
    }

    public function visit(IVisitor $visitor){
        $visitor->entering($this);
    	$visitor->visit($this);
        foreach ($this->children as $child) {
            $child->visit($visitor);
        }
        $visitor->leaving($this);
    }

    public static function makeTreeByDepth($datas, $converter){
        $roots = array();
        $stack = array();
        foreach ($datas as $data) {
            if (!$converter->validate($data)) {
                continue;
            }
            $obj = new PComposite(null, $data);
            $obj_depth = $converter->depth($data);
            if ($obj_depth<= 0) {
                // root object
                $roots[] = $obj;
                $stack = array($obj);
            } else {
                $stack_last = $stack[count($stack)-1];
                $stack_depth = $converter->depth($stack_last->fields);
                if ($stack_depth < $obj_depth) {
                    // 0->1 add child
                } else if ($stack_depth > $obj_depth) {
                    // 1->0 rollback to parent
                    while ($stack_depth >= $obj_depth) {
                        array_pop($stack);
                        $stack_last = $stack[count($stack)-1];
                        $stack_depth = $converter->depth($stack_last->fields);
                    }
                } else {
                    // 1->1 replace last element of stack
                    array_pop($stack);
                    $stack_last = $stack[count($stack)-1];
                }
                // add new object
                $stack_last->add($obj);
                $obj->set_parent($stack_last);
                $stack[] = $obj;
            }
        }
        return $roots;
    }
}
